/*
 * A simple ToDo Manager
 *
 * Copyright (c) 2011 Nosheen Zaza, Manuel Rebollo, Seyedsaeid Mahpeikar and
 * Wo Ke
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package todomanager.data;

import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import todomanager.model.Task;

/**
 * Reads and writes task elements to an XML file. <br/>
 * This is a singleton class. It is so to make it easier to all clients
 * to access the only instance through the call {@code DataManager.getInstance()},
 * and to prevent having multiple file access conflicts. <br/>
 * It uses the JDOM supporting library, and thus it does not try to provide 
 * tools for accessing, reading or creating XML files.
 * It rather does the specific tasks needed for our application.
 * Since JDOM takes care of all XML operations. <br/>
 * For performance reasons, only newly added tasks are saved immediately to
 * the XML file, while edit and delete changes are saved when the application
 * is closed.
 *
 * @author
 * Manuel Rebollo
 * @author
 * Nosheen Zaza
 * @author
 * Seyedsaeid Mahpeikar
 */
public class DataManager {

    private Document taskDoc;
    private static DataManager dataManager;

    /**
     * Holds possible status values of a task.
     */
    private static enum Status {

        DONE, UNDONE
    };

    /*
     * Since we want to allow the user in future releases to enter any desired
     * category and priority, we do not specify then as enums, and allow any
     * strings to represent them.
     */
    private InputStream xmlInputStream;
    /**
     * Specifies the path of the user data stored on the user machine.
     */
    private final String xmlPath = System.getProperty("user.home")
            + ResourceBundle.getBundle(
            "todomanager/data/dataPreferences").getString("userDataPath");

    /**
     * Creates a document and loads the xml file to it
     */
    private DataManager() {
        try {
            File xmlFile = new File(xmlPath);
            /*
             * When running the application for the first time, copy
             * the XML file with sample tasks to a specified directory in
             * the user's home folder.
             */
            if (!xmlFile.exists()) {
                xmlFile.getParentFile().mkdirs();
                xmlInputStream = DataManager.class.getResourceAsStream("taskLists.xml");
                System.out.println(DataManager.class.getResource("taskLists.xml").getFile());
                FileOutputStream fos = new FileOutputStream(xmlPath);
                int readInt;
                while ((readInt = xmlInputStream.read()) != -1) {
                    fos.write(readInt);
                }
            }
            xmlInputStream = new FileInputStream(xmlPath);
            taskDoc = new Document();
            SAXBuilder builder = new SAXBuilder();

            try {
                taskDoc = builder.build(xmlInputStream);
            } catch (JDOMException ex) {
                Logger.getLogger(DataManager.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(DataManager.class.getName()).log(Level.SEVERE, null, ex);
            }
        } catch (FileNotFoundException fnex) {
            Logger.getLogger(DataManager.class.getName()).log(Level.SEVERE, null, fnex);
        } catch (IOException ioex) {
            Logger.getLogger(DataManager.class.getName()).log(Level.SEVERE, null, ioex);
        }
    }

    /**
     * Returns the count of task tags in the xml file.
     * @return the count of task tags.
     */
    public int getTaskCount() {
        return taskDoc.getRootElement().getChildren().size();

    }

    /**
     * Returns the only instance of {@code DataManager}.
     * @return The only {@code DataManager} instance.
     */
    public synchronized static DataManager getInstance() {
        if (dataManager == null) {
            dataManager = new DataManager();
        }
        return dataManager;
    }

    /**
     * Prevents cloning this singleton class.
     * @return Object
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone()
            throws CloneNotSupportedException {
        throw new CloneNotSupportedException();

    }

    /**
     * Saves the contents of {@code taskDoc} to the XML file.
     */
    public void save() {
        try {
            XMLOutputter outputter = new XMLOutputter();
            FileOutputStream file = new FileOutputStream(xmlPath);
            outputter.output(taskDoc, file);
            file.flush();
            file.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Adds a new task to the XML document and saves it immediately.
     * @param task The task to be added.
     */
    public void addTask(Task task) {
        // Gets the root tag
        Element rootTag = taskDoc.getRootElement();

        Element taskTag = new Element("task");
        // Adds attributes
        taskTag.setAttribute("priority", "" + (task.getPriority()));
        taskTag.setAttribute("category", "" + (task.getCategory()));
        taskTag.setAttribute("status", task.isDone()
                ? Status.DONE.toString() : Status.UNDONE.toString());
        taskTag.setAttribute("text", task.getText());
        taskTag.setAttribute("progress", "" + task.getProgress());

        /*
         * Since we allow null dats, we need to handle them carefully.
         * We do not want to allow null dates to be processed.
         */
        if (task.getDueDate() != null) {
            taskTag.setAttribute("date", "" + task.getDueDate().get(
                    GregorianCalendar.YEAR)
                    + " " + task.getDueDate().get(GregorianCalendar.MONTH)
                    + " " + task.getDueDate().get(GregorianCalendar.DATE));
        }

        // Adds the new task as child to the root tag.
        rootTag.addContent(taskTag);
        save();
    }
    
    /**
     * Deletes a task from {@code taskDoc}.
     * @param task
     */
    public void deleteTask(Task task) {
        // Get the root tag
        Element rootTag = taskDoc.getRootElement();
        List<Element> tasks = rootTag.getChildren();
        tasks.remove(task.getId());
    }

    /**
     * Edits a task's text.
     * @param task
     * @param text
     */
    public void editTaskText(Task task, String text) {
        Element taskElement = getTaskElementAt(task.getId());
        taskElement.setAttribute("text", text);
    }

    /**
     * Edits a task's status.
     * @param task
     * @param done a boolean representing status , done == true, undone == false.
     */
    public void editTaskStatus(Task task, boolean done) {
        Element taskElement = getTaskElementAt(task.getId());
        taskElement.setAttribute("status", done ? "done" : "undone");
    }

    /**
     * Edits a task's progress.
     * @param task
     * @param progress
     */
    public void editTaskProgress(Task task, int progress) {
        Element taskElement = getTaskElementAt(task.getId());
        taskElement.setAttribute("progress", "" + progress);
    }

    /**
     * Edits a task's category
     * @param task
     * @param category
     */
    public void editTaskCategory(Task task, String category) {
        Element taskElement = getTaskElementAt(task.getId());
        taskElement.setAttribute("category", category);
    }

    /**
     * Edits  task's priority
     * @param task
     * @param priority
     */
    public void editTaskPriority(Task task, String priority) {
        Element taskElement = getTaskElementAt(task.getId());
        taskElement.setAttribute("priority", priority);
    }

    /**
     * Edits a task's date. if the year is -1, the date is set to
     * an empty string.
     * @param task
     * @param year
     * @param month
     * @param day
     */
    public void editTaskDate(Task task, int year, int month, int day) {
        Element taskElement = getTaskElementAt(task.getId());
        if(year != -1)
            taskElement.setAttribute("date", year + " " + month + " " + day);
        else
            taskElement.removeAttribute("date");
    }

    /**
     * Returns a task element at the given index within a tasklist tag,
     * null if there is not task at the given index.
     * @param index
     * @return
     */
    public Element getTaskElementAt(int index) {
        Element rootTag = taskDoc.getRootElement();
        List<Element> tasks = rootTag.getChildren();
        if (index < tasks.size()) {
            return tasks.get(index);
        }
        return null;
    }

    /**
     * Returns all currently stored tasks in the XML file.
     * @return {@code ArrayList} of all stored tasks.
     */
    public ArrayList<Task> getAllTasks() {
        ArrayList<Task> taskList = new ArrayList<Task>();
        Element rootTag = taskDoc.getRootElement();
        List<Element> tasks = rootTag.getChildren();

        /*
         * Our tasks have run-time id's only. This makes it easier to handle
         * task deletion. A task's id is its position in a list here
         * we initialize the ids, but they might change later.
         */

        int id = 0;
        /* An enhanced for loop actually uses iterators, this is why we
         * did not replace them with iterators.
         */
        for (Element taskElement : tasks) {
            Task task = new Task();
            task.setId(id++);
            task.setDone(
                    taskElement.getAttributeValue("status").equalsIgnoreCase(
                    Status.DONE.toString()) ? true : false);
            task.setCategory(taskElement.getAttributeValue("category"));
            task.setPriority(taskElement.getAttributeValue("priority"));
            task.setText(taskElement.getAttributeValue("text"));
            task.setProgress(Integer.parseInt(taskElement.getAttributeValue("progress")));

            if (taskElement.getAttributeValue("date") != null) {
                StringTokenizer st = new StringTokenizer(taskElement.getAttributeValue("date"));
                task.setDueDate(new GregorianCalendar(Integer.parseInt(st.nextToken()),
                        Integer.parseInt(st.nextToken()),
                        Integer.parseInt(st.nextToken())));
            }


            taskList.add(task);
        }
        return taskList;
    }

    /**
     * Deletes all tasks from {@code taskDoc}.
     */
    public void deleteAllTasks() {
        Element rootTag = taskDoc.getRootElement();
        rootTag.removeContent();
    }
}
